package common;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

//An object recording player statistics for each player.
//A player object is intended to be unique for each round.
public class Player extends Updating implements Sendable, Comparable<Player> {

	private static final long serialVersionUID = 4517152528188963012L;
	
	private static final AtomicInteger instanceCount = new AtomicInteger(0);
	private final int uniqueID;
	
	private String name;
	private PlayerLimits limit;
		//This represents the amount actually sold/bought.
	private EnumMap<Commodity,Integer> sold;
	
		// This also represents bought. Stores the quantities for the current 
		// asks/bids this player may have.
	private EnumMap<Commodity,Integer> maybeSold;
	private EnumMap<Commodity,AuctionList> bids;
		// Could also represent asks.
	private volatile double score = 0;
	
	private transient Round currentRound;
	private boolean isReady = false;
	
	public Player (String name, PlayerLimits limit){
		
		uniqueID = instanceCount.getAndIncrement();
		
		this.name = name;
		this.limit = limit;
		bids = new EnumMap<Commodity,AuctionList>(Commodity.class);
		sold = new EnumMap<Commodity,Integer>(Commodity.class);
		maybeSold = new EnumMap<Commodity,Integer>(Commodity.class);
		for (Commodity c : Commodity.values()) {
			sold.put(c,0);
			maybeSold.put(c,0);
			bids.put(c, new AuctionList());
		}
	}
	
	public Player(String name) {
		this(name,null);
		limit = new PlayerLimits(true);
	}
	
	public boolean ownsAuction( Auction a ) {
		return a.ownerName.equals( name );
	}
	
	//Determines whether a player can buy/sell a commodity.
	public boolean canAllow( Auction a ) {
		if (limit == null) return false;
		CommodityLimits cL = limit.get(a.commodity);
		if (cL == null) return false;
		//Validity Check
		synchronized(this) {
			if (a.quantity > cL.quota - sold.get(a.commodity) - maybeSold.get(a.commodity)) return false;
		}
		//Validity Check 2
		return a.isValid(limit.get(a.commodity));
	}
	
	//PRE : canAllow()
	public synchronized void buyOrSell (Auction myAuction, Auction otherAuction) {
		Auction a = myAuction;
		int i = sold.get(a.commodity);
		sold.put(a.commodity,i+a.quantity);
		if (limit != null) score += Math.abs(myAuction.getMatchPrice(otherAuction) - limit.get(a.commodity).bound)*a.quantity;

	}
	
	//Returns a matching auction object or null if it exists.
	//and removes it from the list.
	// From must be the player which has requested a.
	public Auction match(Auction a, Player from) {
		Commodity c = a.commodity;
		AuctionList L = bids.get( c );
		
//		System.out.println("LSize: " + L.size() + " for " + this.name );
		
		//Check whether or not the auction from the player is a buyer or seller
		boolean argumentIsBid = from.limit.get( c ).isBuyer;
		
		//Make sure that there is one seller and one buyer.
		if (limit.get(c).isBuyer == argumentIsBid) return null;
		
		//Note(Ryan): I don't think this check is correct? but maybe
//		if (!canAllow(a)) return null;
		
		Auction max = null;
		Auction min = null;
		synchronized(L) {
			for (Auction b : L) {
				if (!b.isValidMatch(a) || !a.isValidMatch(b))
					continue;
					
				if (max == null || max.getMatchPrice(a) < b.getMatchPrice(a)) 
					max = b;
				if (min == null || min.getMatchPrice(a) > b.getMatchPrice(a))
					min = b;
			}
		}
		//If the argument was from a buyer, return the min price, otherwise return max price
		return (argumentIsBid) ? min : max;
	}
	//PRE : match() returned non null in the past.
	public synchronized boolean removeAuction(Auction a) {
		AuctionList L = bids.get(a.commodity);
		boolean success = false;
		synchronized(L) {
			success = L.remove(a);
		}
		
		synchronized(this) {
			if (success) maybeSold.put(a.commodity, maybeSold.get(a.commodity)-a.quantity);
		}
		return success;
	}
	
	public void cancelAllAuctions() {
		for( Commodity c : Commodity.values() ) {
			AuctionList auctions = bids.get( c );
			
			synchronized( auctions ) {
				for( Auction a : auctions )
					a.cancelExpiration();
			}
			
		}
	}
	
	public synchronized boolean canAllowAndAdd( Auction a ) {
		if (!canAllow(a)) return false;
		addAuction(a);
		return true;
	}
		
	public synchronized void addAuction (Auction a) {
		AuctionList L = bids.get( a.commodity );
		synchronized(L) {
			L.add( a );
		}
		synchronized(this) {
			maybeSold.put(a.commodity, maybeSold.get(a.commodity)+a.quantity);
		}
	}
	
	
	public CommodityLimits getLimits(Commodity c){
		return (limit == null) ? null : limit.get(c);
	}
	
	public PlayerLimits getLimits() {
		return limit;
	}
	
	//Return the # of items sold or bought of c.
	public synchronized int getSold(Commodity c) {
		return sold.get(c);
	}
	//Return the current # of items in asks or bids.
	public synchronized int getMaybeSold(Commodity c) {
		return maybeSold.get(c);
	}
	
	public void setLimits( PlayerLimits limits ) {
		this.limit = limits;
	}
	
	public double getScore() {
		return score;
	}	
	
	public int hashCode() {
		return name.hashCode();
	}
	
	public int compareTo(Player p) {
		return (this.score == p.score) ? this.name.compareTo(p.name) : (this.score > p.score) ? -1 : 1;
	}
	
	public String getType() {
		return "PlayerResponse";
	}
	
	public void setName( String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public void setRound (Round r) {
		currentRound = r;
	}
	
	public Round getRound() {
		return currentRound;
	}
	
	public boolean isReady() {
		return isReady;
	}
	
	public void setIsReady( boolean ready ) {
		isReady = ready;
	}

	public void update (Player p) {
		this.limit = p.limit;
		this.sold = p.sold;
		this.maybeSold = p.maybeSold;
		this.score = p.score;
		this.setUpdated();
	}
	
	public boolean equals (Player p) {
		return this.name.equals(p.name) && this.uniqueID == p.uniqueID;
	}
	public String toString() {
		return name.toString() + " : " + score;
	}
	
	public boolean isDone() {
		for (Commodity c : Commodity.values()) {
			int i = sold.get(c);
			int j = limit.get(c).quota;
			if (i != j) return false;
		}
		return true;
	}
	
	public Player getLarger( Player p ) {
		if( this.uniqueID > p.uniqueID )
			return this;
		else
			return p;
	}
	
	private class AuctionList extends LinkedList<Auction>{
		private static final long serialVersionUID = 7902337260196220952L;
	}
}
